#include "main.h"

#include <stdlib.h>
//using namespace std;

#include <fstream>
#include <string>
using namespace std;
#include <iostream>
#include <sstream>
#include <time.h>
#include <getopt.h>
#include <math.h>
#include <assert.h>
//#include <mpi.h>
#include <float.h>

#include "SFMT.h"
#include "SFMT.c"
#include <OpenCL/cl.h>
#include "cl.hpp"
#include "tinymt32def.h"
#include "clDebug.c"

int main (int argc, char *argv[]) {

	//cl::Buffer tinymt_status = get_param_buff("wtf.out", 1000);

	
	clock_t timer=clock();

size_t source_size,ret_size; //i,j=0,p,q,k=0,l,r=0,c=0,r_d=0,c_d=0,ret_size,total_alloc=0,total_mem=0;


cl_device_type DEVICE_TYPE=CL_DEVICE_TYPE_CPU;

cl_uint ret_num_devices, ret_num_platforms;
cl_int ret;
cl_device_id *device_id;
cl_kernel kernel=NULL;
cl_command_queue queue=NULL;
cl_event event=NULL;

cl_mem cl_mem_data, cl_mem_d, cl_mem_n, cl_mem_return_data, cl_mem_mt32_status;
// cl_ulong /**dev_mem,*dev_alloc,event_end,event_start,event_max,event_total=0;


char *source_str, *source_f=new char[LINE_LEN*LINE_LEN], *pbuf;
sprintf(source_f,"functions.cl");
FILE *fp=NULL;



int optc,platform=0,device=0;

static struct option const longopts[] = {
	{"device", required_argument, NULL, 'd'},
	{"function",required_argument,NULL,'f'},
	{"generate",required_argument,NULL,'g'},
	{"help", no_argument, NULL, 'h'},
	{"version",no_argument,NULL,'v'},
	{NULL,no_argument,NULL,0} 
};

while ((optc=getopt_long (argc, argv, "d:f:g:hv", longopts, NULL)) != -1)
	switch (optc) {
		case 'd': //select device type... for future OpenCL implementation
			if (strcasecmp("cpu",optarg)==0) 
				DEVICE_TYPE=CL_DEVICE_TYPE_CPU;
			else if (strcasecmp("gpu",optarg)==0) 
				DEVICE_TYPE=CL_DEVICE_TYPE_GPU;
			else if (strcasecmp("all",optarg)==0) 
				DEVICE_TYPE=CL_DEVICE_TYPE_ALL;
			cerr<<"DEVICE_TYPE="<<DEVICE_TYPE<<endl;	
			break;
		case 'f':
			sprintf(source_f,"%s",optarg);
			cerr<<"Setting source_f='"<<source_f<<"'..."<<endl;
			break;	
		case 'g': //generate test data and exit
						//n=atoi(strtok(optarg,","));
			//m=atoi(strtok(NULL,","));

			/*assert(n>0 && m>0);
			if (mpi_rank==0) {
				d_emperical=generate_emperical(m,n);
				write_Rmn(d_emperical,cout);
			}*/
			exit(EXIT_SUCCESS);
			break;
		case 'h': // print help menu and exit;
			cerr<<"Help!"<<endl;
			exit(EXIT_SUCCESS);
			break; 
		case 'v': 
			cerr<<"Version 2014-1"<<endl; 
			exit(EXIT_SUCCESS);
			break;
	}

assert(fp=fopen(source_f, "r"));
free(source_f);

char line[LINE_LEN], *ptr_a=NULL;
float tmp_data[LINE_LEN][LINE_LEN];

uint d=0, n=0;
for  (n=0;fgets(line,LINE_LEN,stdin);) {
	if (line[0]!='#') {
		tmp_data[n][0]=atof(strtok(line,DELIM)); // first 
		for (d=1;(ptr_a=strtok(NULL,DELIM))!=NULL;d++)
			tmp_data[n][d]=atof(ptr_a);
		n++;
	}
}

assert(d<=MAX_DIM && n<=MAX_SAMPLES);

float *data=new float[n*d];
for (uint i=0;i<n;i++)
	for (uint j=0;j<d;j++)
		data[i*d+j]=tmp_data[i][j];
cerr<<"Loaded "<<n<<"x"<<d<<" matrix..."<<endl;

cerr<<"Selecting platform...";
clDebug(clGetPlatformIDs(0,NULL,&ret_num_platforms),"GetPlatformIDs");

cl_platform_id platform_ids[ret_num_platforms];

clDebug(clGetPlatformIDs(ret_num_platforms,platform_ids,NULL),"GetPlatformIDs");

assert(ret_num_platforms);

clDebug(clGetPlatformInfo(platform_ids[platform],CL_PLATFORM_VERSION, 0, NULL, &ret_size),"clGetPlatformInfo");
pbuf=new char[ret_size+1];
clDebug(clGetPlatformInfo(platform_ids[platform],CL_PLATFORM_VERSION, ret_size,pbuf, NULL),"clGetPlatformInfo");
fprintf(stderr," \"%s\" in %g seconds\n",pbuf,((double) (clock()- timer)) / CLOCKS_PER_SEC);
free(pbuf);

timer=clock();
fprintf(stderr,"Selecting device... ");


clDebug(clGetDeviceIDs(platform_ids[platform], DEVICE_TYPE,0, NULL, &ret_num_devices),"clGetDeviceIDs"); // add a clDebug!!

assert(ret_num_devices);
device_id=new cl_device_id[ret_num_devices];// =malloc(ret_num_devices*sizeof(cl_device_id));

clDebug(clGetDeviceIDs(platform_ids[platform], DEVICE_TYPE,ret_num_devices, device_id, NULL),"clGetDeviceIDs");


clGetDeviceInfo(device_id[device],CL_DEVICE_NAME,0,NULL,&ret_size); //FIXME
pbuf=new char[ret_size+1];
clGetDeviceInfo(device_id[device],CL_DEVICE_NAME,ret_size,pbuf,NULL);


cerr<<" \""<<pbuf<<"\""<<endl;




source_str = (char*)malloc(MAX_SOURCE_SIZE);
source_size = fread( source_str, 1, MAX_SOURCE_SIZE, fp);
fclose(fp);



clDebug(clGetDeviceIDs(platform_ids[platform], DEVICE_TYPE,ret_num_devices, device_id, NULL),"clGetDeviceIDs");


// Create an OpenCL context
 cl_context context = clCreateContext( NULL, ret_num_devices, device_id, NULL, NULL, &ret);
 clDebug(ret,"clCreateContext");

 
 queue=clCreateCommandQueue(context, device_id[device], CL_QUEUE_PROFILING_ENABLE, &ret);
clDebug(ret,"clCreateCommandQueue");


cl_mem_data=clCreateBuffer(context, CL_MEM_READ_ONLY, d*n*sizeof(float), NULL, &ret);
clDebug(ret,"clCreateBuffer");

cl_mem_n=clCreateBuffer(context, CL_MEM_READ_ONLY, sizeof(uint), NULL, &ret);
clDebug(ret,"clCreateBuffer");

cl_mem_d=clCreateBuffer(context, CL_MEM_READ_ONLY, sizeof(uint), NULL, &ret);
clDebug(ret,"clCreateBuffer");

cl_mem_return_data=clCreateBuffer(context, CL_MEM_READ_WRITE, ACCEPTANCES*d*sizeof(float), NULL, &ret);
clDebug(ret,"clCreateBuffer");


cl_mem_mt32_status=clCreateBuffer(context, CL_MEM_READ_ONLY, sizeof(tinymt32wp_t)*ACCEPTANCES,NULL,&ret);
clDebug(ret,"clCreateBuffer");


clDebug(clEnqueueWriteBuffer(queue,cl_mem_data,CL_TRUE,0,d*n*sizeof(float),data,0,NULL,&event),"clEnqueueWriteBuffer");
clDebug(clEnqueueWriteBuffer(queue,cl_mem_n,CL_TRUE,0,sizeof(uint),&n,0,NULL,&event),"clEnqueueWriteBuffer");
clDebug(clEnqueueWriteBuffer(queue,cl_mem_d,CL_TRUE,0,sizeof(uint),&d,0,NULL,&event),"clEnqueueWriteBuffer");



 cl_program program = clCreateProgramWithSource(context, 1, (const char **)&source_str, (const size_t *)&source_size, &ret);
 clDebug(ret,"clCreateProgramWithSource");

 free(source_str);

ret=clBuildProgram(program, ret_num_devices, device_id,NULL, NULL, NULL);
	clDebug(clGetProgramBuildInfo(program, device_id[device], CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_size),"clGetProgramBuildInfo");
	pbuf=new char[ret_size+1];
	clDebug(clGetProgramBuildInfo(program, device_id[device], CL_PROGRAM_BUILD_LOG, ret_size, pbuf,NULL),"clGetProgramBuildInfo");
	cerr<<"ProgramBuildInfo=\""<<pbuf<<"\""<<endl;
	delete pbuf;
clDebug(ret,"clBuildProgram");


kernel=clCreateKernel(program,"generate",&ret);
clDebug(ret,"clCreateKernel");


clDebug(clSetKernelArg(kernel, 0, sizeof(cl_mem), (void*)&cl_mem_data),"clSetKernelArg");
clDebug(clSetKernelArg(kernel, 1, sizeof(cl_mem), (void*)&cl_mem_n),"clSetKernelArg");
clDebug(clSetKernelArg(kernel, 2, sizeof(cl_mem), (void*)&cl_mem_d),"clSetKernelArg");
clDebug(clSetKernelArg(kernel, 3, sizeof(cl_mem), (void*)&cl_mem_return_data),"clSetKernelArg");
clDebug(clSetKernelArg(kernel, 4, sizeof(cl_mem), (void*)&cl_mem_mt32_status),"clSetKernelArg");


size_t local=LOCAL, global=GLOBAL;

clDebug(clEnqueueNDRangeKernel(queue, kernel, 1,NULL,&global,&local,0,NULL,&event),"clEnqueueNDRangeKernel");


clDebug(clWaitForEvents(1,&event),"clWaitForEvents");
//clReleaseEvent(event);


float *return_data=new float[d*ACCEPTANCES];
clDebug(clEnqueueReadBuffer(queue,cl_mem_return_data,CL_TRUE,0,d*ACCEPTANCES*sizeof(float),return_data,0,NULL,&event),"clEnqueueReadBuffer");

clDebug(clWaitForEvents(1,&event),"clWaitForEvents");
clReleaseEvent(event);

for (uint i=0;i<ACCEPTANCES;i++) {
	cout<<return_data[i*d];
	for (uint j=1;j<d;j++)
			cout<<"\t"<<return_data[i*d+j];
	cout<<endl;
}
delete return_data;

exit(EXIT_SUCCESS);
}
